#include "ThreadPool.h"
#include "NamedThreadContext.h"

namespace Alice {
	int32 bHasBackgroundThreads = CREATE_BACKGROUND_TASK_THREADS;
	int32 bHasHighPriorityThreads = CREATE_HIPRI_TASK_THREADS;
	static ThreadPool* sThreadPoolSingleton=nullptr;
	ThreadPoolInterface& ThreadPoolInterface::Get()
	{
		if(sThreadPoolSingleton==nullptr)
		{
			sThreadPoolSingleton=new ThreadPool;
		}
		return *sThreadPoolSingleton;
	}
	void ThreadPool::Startup(int32 inNumThreads)
	{
		bCreatedHiPriorityThreads = !!bHasHighPriorityThreads;
		bCreatedBackgroundPriorityThreads = !!bHasBackgroundThreads;
		{
			mTLSSlot = PlatformTLS::AllocTlsSlot();
			for (int32 ThreadIndex = 0; ThreadIndex < 4; ThreadIndex++)
			{
				CompositedThreads[ThreadIndex].mThreadContext = new NamedPoolContext;
				CompositedThreads[ThreadIndex].mThreadContext->Setup(ENamedThreads(ThreadIndex), mTLSSlot, &CompositedThreads[ThreadIndex]);
			}
		}
		int32 NumTaskThreads = inNumThreads;
		LastExternalThread = ENamedThreads::ActualRenderingThread;
		NumNamedThreads = (int32)LastExternalThread + 1;
		NumTaskThreadSets = 1 + bCreatedHiPriorityThreads + bCreatedBackgroundPriorityThreads;
		NumThreads = NumNamedThreads + NumTaskThreads * NumTaskThreadSets;
		NumTaskThreadsPerSet = (NumThreads - NumNamedThreads) / NumTaskThreadSets;
		ReentrancyCheck.Increment(); // just checking for reentrancy
		mTLSSlot = PlatformTLS::AllocTlsSlot();

		for (int32 ThreadIndex = 0; ThreadIndex < NumThreads; ThreadIndex++)
		{
			bool bAnyTaskThread = ThreadIndex >= NumNamedThreads;
			if (bAnyTaskThread)
			{
				CompositedThreads[ThreadIndex].mThreadContext = new AliceAnyThreadContext(ThreadIndexToPriorityIndex(ThreadIndex));
			}
			else
			{
				CompositedThreads[ThreadIndex].mThreadContext = new NamedPoolContext;
			}
			CompositedThreads[ThreadIndex].mThreadContext->Setup(ENamedThreads(ThreadIndex), mTLSSlot, &CompositedThreads[ThreadIndex]);
		}

		for (int32 ThreadIndex = (int32)LastExternalThread + 1; ThreadIndex < NumThreads; ThreadIndex++)
		{
			CompositedThreads[ThreadIndex].mThread = Thread::Create("WorkerThread",&ThreadContext(ThreadIndex));
			CompositedThreads[ThreadIndex].bAttached = true;
		}
	}
	int ThreadPool::ThreadIndexToPriorityIndex(int32 ThreadIndex)
	{
		int32 Result = (ThreadIndex - NumNamedThreads) / NumTaskThreadsPerSet;
		return Result;
	}
	ThreadPool::ThreadPool()
	{
	}
	void ThreadPool::QueueTask(RunnableTask* Task, ENamedThreads ThreadToExecuteOn, ENamedThreads InCurrentThreadIfKnown)
	{
		if (GetThreadIndex(ThreadToExecuteOn) == ENamedThreads::AnyThread)
		{
			uint32 TaskPriority = GetTaskPriority(Task->mThreadToExecuteOn);
			int32 Priority = GetThreadPriorityIndex(Task->mThreadToExecuteOn);
			if (Priority == ((int32)ENamedThreads::BackgroundThreadPriority >> (int32)ENamedThreads::ThreadPriorityShift) && (!bCreatedBackgroundPriorityThreads))
			{
				Priority = (int32)ENamedThreads::NormalThreadPriority >> (int32)ENamedThreads::ThreadPriorityShift; // we don't have background threads, promote to normal
				TaskPriority = (int32)ENamedThreads::NormalTaskPriority >> (int32)ENamedThreads::TaskPriorityShift; // demote to normal task pri
			}
			else if (Priority == ((int32)ENamedThreads::HighThreadPriority >> (int32)ENamedThreads::ThreadPriorityShift) && (!bCreatedHiPriorityThreads))
			{
				Priority = (int32)ENamedThreads::NormalThreadPriority >> (int32)ENamedThreads::ThreadPriorityShift; // we don't have hi priority threads, demote to normal
				TaskPriority = (int32)ENamedThreads::HighTaskPriority >> (int32)ENamedThreads::TaskPriorityShift; // promote to hi task pri
			}
			uint32 PriIndex = TaskPriority ? 0 : 1;
			{
				int32 IndexToStart = IncomingAnyThreadTasks[Priority].Push(Task, PriIndex);
				if (IndexToStart >= 0)
				{
					StartTaskThread(Priority, IndexToStart);
				}
			}
		}
		ENamedThreads CurrentThreadIfKnown;
		if (GetThreadIndex(InCurrentThreadIfKnown) == ENamedThreads::AnyThread)
		{
			CurrentThreadIfKnown = GetCurrentThread();
		}
		else
		{
			CurrentThreadIfKnown = GetThreadIndex(InCurrentThreadIfKnown);
		}
		{
			int32 QueueToExecuteOn = GetQueueIndex(ThreadToExecuteOn);
			ThreadToExecuteOn = GetThreadIndex(ThreadToExecuteOn);
			PoolThreadContext* Target = &ThreadContext((int32)ThreadToExecuteOn);
			if (ThreadToExecuteOn == GetThreadIndex(CurrentThreadIfKnown))
			{
				Target->EnqueueFromThisThread(QueueToExecuteOn, Task);
			}
			else
			{
				Target->EnqueueFromOtherThread(QueueToExecuteOn, Task);
			}
		}
	}


	int32 ThreadPool::GetNumWorkerThreads()
	{
		int32 Result = (NumThreads - NumNamedThreads) / NumTaskThreadSets;
		return Result;
	}

	ENamedThreads ThreadPool::GetCurrentThreadIfKnown(bool bLocalQueue)
	{
		ENamedThreads Result = GetCurrentThread();
		if (bLocalQueue && (int32)GetThreadIndex(Result) >= 0 && (int32)GetThreadIndex(Result) < NumNamedThreads)
		{
			Result = ENamedThreads(int32(Result) | int32(ENamedThreads::LocalQueue));
		}
		return Result;
	}

	bool ThreadPool::IsThreadProcessingTasks(ENamedThreads ThreadToCheck)
	{
		int32 QueueIndex = GetQueueIndex(ThreadToCheck);
		ThreadToCheck = GetThreadIndex(ThreadToCheck);
		return ThreadContext((int32)ThreadToCheck).IsProcessingTasks(QueueIndex);
	}

	void ThreadPool::AttachToThread(ENamedThreads CurrentThread)
	{
		CurrentThread = GetThreadIndex(CurrentThread);
		ThreadContext((int32)CurrentThread).InitializeForCurrentThread();
	}

	uint64 ThreadPool::ProcessThreadUntilIdle(ENamedThreads inThreadNameAndQueueIndex)
	{
		int32 queueIndex = GetQueueIndex(inThreadNameAndQueueIndex);
		ENamedThreads threadName = ExtractThreadName(inThreadNameAndQueueIndex);
		return ThreadContext((int32)threadName).ProcessTasksUntilIdle(queueIndex);
	}

	void ThreadPool::ProcessThreadUntilManualStop(ENamedThreads inThreadNameAndQueueIndex)
	{
		int32 queueIndex = GetQueueIndex(inThreadNameAndQueueIndex);
		ENamedThreads threadName = ExtractThreadName(inThreadNameAndQueueIndex);
		ThreadContext((int32)threadName).ProcessTasksUntilManualStop(queueIndex);
	}
	void ThreadPool::ProcessThreadUntilRequestReturn(ENamedThreads inThreadNameAndQueueIndex)
	{
		int32 queueIndex = GetQueueIndex(inThreadNameAndQueueIndex);
		ENamedThreads threadName = ExtractThreadName(inThreadNameAndQueueIndex);
		ThreadContext((int32)threadName).ProcessTasksUntilQuit(queueIndex);
	}

	void ThreadPool::RequestReturn(ENamedThreads CurrentThread)
	{
		int32 QueueIndex = GetQueueIndex(CurrentThread);
		CurrentThread = GetThreadIndex(CurrentThread);
		ThreadContext((int32)CurrentThread).RequestQuit(QueueIndex);
	}

	void ThreadPool::WaitUntilTasksComplete(const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown)
	{
		ENamedThreads CurrentThread = CurrentThreadIfKnown;
		if (GetThreadIndex(CurrentThreadIfKnown) == ENamedThreads::AnyThread)
		{
			bool bIsHiPri = !!GetTaskPriority(CurrentThreadIfKnown);
			int32 Priority = GetThreadPriorityIndex(CurrentThreadIfKnown);
			CurrentThreadIfKnown = GetThreadIndex(GetCurrentThread());
			CurrentThread = SetPriorities(CurrentThreadIfKnown, Priority, bIsHiPri);
		}
		else
		{
			CurrentThreadIfKnown = GetThreadIndex(CurrentThreadIfKnown);
		}

		if (CurrentThreadIfKnown != ENamedThreads::AnyThread && (int32)CurrentThreadIfKnown < (int32)NumNamedThreads && !IsThreadProcessingTasks(CurrentThread))
		{
			if (Tasks.size() < 8) // don't bother to check for completion if there are lots of prereqs...too expensive to check
			{
				bool bAnyPending = false;
				for (int32 Index = 0; Index < Tasks.size(); Index++)
				{
					const RunnableTaskEventRef& Task = Tasks[Index];
					if (Task.GetReference() && !Task->IsComplete())
					{
						bAnyPending = true;
						break;
					}
				}
				if (!bAnyPending)
				{
					return;
				}
			}
			// named thread process tasks while we wait
			TRunnableTask<ThreadReturnTask>::CreateTask(&Tasks, CurrentThread).ConstructAndDispatchWhenReady(CurrentThread);
			ProcessThreadUntilRequestReturn(CurrentThread);
		}
		else
		{
			ScopedEvent Event;
			TriggerEventWhenTasksComplete(Event.Get().mPtr, Tasks, CurrentThreadIfKnown);
		}
	}

	void ThreadPool::WaitUntilTasksComplete(ENamedThreads CurrentThreadIfKnown)
	{
		ENamedThreads CurrentThread = CurrentThreadIfKnown;
		if (GetThreadIndex(CurrentThreadIfKnown) == ENamedThreads::AnyThread)
		{
			bool bIsHiPri = !!GetTaskPriority(CurrentThreadIfKnown);
			int32 Priority = GetThreadPriorityIndex(CurrentThreadIfKnown);
			CurrentThreadIfKnown = GetThreadIndex(GetCurrentThread());
			CurrentThread = SetPriorities(CurrentThreadIfKnown, Priority, bIsHiPri);
		}
		else
		{
			CurrentThreadIfKnown = GetThreadIndex(CurrentThreadIfKnown);
		}
		Debug("WaitUntilTasksComplete");
		ScopedEvent Event;
		TRunnableTask<FinishThreadWorkTask>::CreateTask(nullptr, CurrentThreadIfKnown).ConstructAndDispatchWhenReady(Event.Get().mPtr, CurrentThreadIfKnown);
	}

	void ThreadPool::TriggerEventWhenTasksComplete(Event* InEvent, const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown, ENamedThreads TriggerThread)
	{
		bool bAnyPending = true;
		if (Tasks.size() < 8) // don't bother to check for completion if there are lots of prereqs...too expensive to check
		{
			bAnyPending = false;
			for (int32 Index = 0; Index < Tasks.size(); Index++)
			{
				const RunnableTaskEventRef & Task = Tasks[Index];
				if (Task.mPtr && !Task->IsComplete())
				{
					bAnyPending = true;
					break;
				}
			}
		}
		if (!bAnyPending)
		{
			InEvent->TriggerAll();
			return;
		}
		TRunnableTask<FTriggerEventGraphTask>::CreateTask(&Tasks, CurrentThreadIfKnown).ConstructAndDispatchWhenReady(InEvent, TriggerThread);
	}

	void ThreadPool::AddShutdownCallback(void(*Callback)())
	{
		ShutdownCallbacks.push_back(Callback);
	}

	void ThreadPool::WakeNamedThread(ENamedThreads ThreadToWake)
	{
		const ENamedThreads ThreadIndex = GetThreadIndex(ThreadToWake);
		if ((int32)ThreadIndex < NumNamedThreads)
		{
			ThreadContext((int32)ThreadIndex).WakeUp(GetQueueIndex(ThreadToWake));
		}
	}

	void ThreadPool::StartTaskThread(int32 Priority, int32 IndexToStart)
	{
		ENamedThreads ThreadToWake = ENamedThreads(IndexToStart + Priority * NumTaskThreadsPerSet + NumNamedThreads);
		((AliceAnyThreadContext&)ThreadContext((int32)ThreadToWake)).WakeUp();
	}
	void ThreadPool::StartAllTaskThreads(bool bDoBackgroundThreads)
	{
		for (int32 Index = 0; Index < GetNumWorkerThreads(); Index++)
		{
			for (int32 Priority = 0; Priority < (int32)ENamedThreads::NumThreadPriorities; Priority++)
			{
				if (Priority == ((int32)ENamedThreads::NormalThreadPriority >> (int32)ENamedThreads::ThreadPriorityShift) ||
					(Priority == ((int32)ENamedThreads::HighThreadPriority >> (int32)ENamedThreads::ThreadPriorityShift) && bCreatedHiPriorityThreads) ||
					(Priority == ((int32)ENamedThreads::BackgroundThreadPriority >> (int32)ENamedThreads::ThreadPriorityShift) && bCreatedBackgroundPriorityThreads && bDoBackgroundThreads)
					)
				{
					StartTaskThread(Priority, Index);
				}
			}
		}
	}

	RunnableTask* ThreadPool::FindWork(ENamedThreads ThreadInNeed)
	{
		int32 LocalNumWorkingThread = GetNumWorkerThreads();
		int32 MyIndex = int32((uint32(ThreadInNeed) - NumNamedThreads) % NumTaskThreadsPerSet);
		int32 Priority = int32((uint32(ThreadInNeed) - NumNamedThreads) / NumTaskThreadsPerSet);
		return IncomingAnyThreadTasks[Priority].Pop(MyIndex, true);
	}
	ENamedThreads ThreadPool::GetCurrentThread()
	{
		ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread;
		CompositedThread* compositedThread = (CompositedThread*)PlatformTLS::GetTlsValue(mTLSSlot);
		if (compositedThread!=nullptr)
		{
			int32 ThreadIndex = ALICE_PTRDIFF_TO_INT32(compositedThread - CompositedThreads);
			if (ThreadIndex < NumNamedThreads)
			{
				CurrentThreadIfKnown = ENamedThreads(ThreadIndex);
			}
			else
			{
				int32 Priority = (ThreadIndex - NumNamedThreads) / NumTaskThreadsPerSet;
				CurrentThreadIfKnown = SetPriorities(ENamedThreads(ThreadIndex), Priority, false);
			}
		}
		return CurrentThreadIfKnown;
	}

}
